import { exec } from 'node:child_process';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { rimraf } from 'rimraf';
import {
  type RegistryItem,
  registryItemSchema,
  type Registry,
} from 'shadcn/registry';
import { z } from 'zod';

import { registryBlocks } from '@/registry/registry-blocks';
import { registryLib } from '@/registry/registry-lib';
import { registryUI } from '@/registry/registry-ui';
import { registryExamples } from '@/registry/registry-examples';
import { registryHooks } from '@/registry/registry-hooks';
import { registryComponents } from '@/registry/registry-components';
import { registryInit } from '@/registry/registry';
import { registryStyles } from '@/registry/registry-styles';
import { buildDocsRegistry } from './build-docs-registry.mts';

const HOMEPAGE = 'https://platejs.org';
const NAME = 'plate';
const BASE_URL = 'src/';

const isDev = process.env.NODE_ENV === 'development';
const MERGE_DOCS = true;
const REGISTRY_URL = isDev ? 'http://localhost:3000/rd' : `${HOMEPAGE}/r`;
const TARGET = isDev ? 'public/rd/registry.json' : 'public/r/registry.json';

const registry: Registry = {
  name: NAME,
  homepage: HOMEPAGE,
  items: z.array(registryItemSchema).parse(
    [
      ...registryInit,
      ...registryUI,
      ...registryComponents,
      ...registryBlocks.map((block) => ({
        ...block,
        registryDependencies: [
          'plate-ui',
          ...(block.registryDependencies ?? []),
        ],
      })),
      ...registryLib,
      ...registryStyles,
      ...registryHooks,
      ...registryExamples,
    ].map((item) => ({
      ...item,
      registryDependencies: item.registryDependencies?.map((dep) =>
        dep.startsWith('shadcn/')
          ? dep.split('shadcn/')[1]
          : `${REGISTRY_URL}/${dep}`
      ),
    }))
  ),
} satisfies Registry;

async function buildRegistryIndex() {
  let index = `// @ts-nocheck
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
import * as React from "react"

export const Index: Record<string, any> = {`;
  for (const item of registry.items) {
    const resolveFiles = item.files?.map((file) => `registry/${file.path}`);
    if (!resolveFiles) {
      continue;
    }

    const componentPath =
      !item.meta?.rsc && item.files?.[0]?.path
        ? `@/registry/${item.files[0].path}`
        : '';

    index += `
  "${item.name}": {
    name: "${item.name}",
    description: "${item.description ?? ''}",
    type: "${item.type}",
    registryDependencies: ${JSON.stringify(item.registryDependencies)},
    files: [${item.files?.map((file) => {
      const filePath = `${BASE_URL}registry/${typeof file === 'string' ? file : file.path}`;
      const resolvedFilePath = path.resolve(filePath);
      return typeof file === 'string'
        ? `"${resolvedFilePath}"`
        : `{
      path: "${filePath}",
      type: "${file.type}",
      target: "${file.target ?? ''}"
    }`;
    })}],
    component: ${
      componentPath
        ? `React.lazy(async () => {
      const mod = await import("${componentPath}")
      const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name
      return { default: mod.default || mod[exportName] }
    })`
        : 'null'
    },
    meta: ${JSON.stringify(item.meta)},
  },`;
  }

  index += `
  }`;

  // Write style index.
  rimraf.sync(path.join(process.cwd(), `${BASE_URL}__registry__/index.tsx`));
  await fs.writeFile(
    path.join(process.cwd(), `${BASE_URL}__registry__/index.tsx`),
    index
  );
}

function sanitizeRegistry(registry: Registry): Registry {
  return {
    ...registry,
    items: registry.items
      // Filter internal examples.
      .filter((item) => item.meta?.registry !== false)
      .map((item) => {
        const files = item.files?.map((file) => ({
          ...file,
          path: `${BASE_URL}registry/${file.path}`,
        }));

        return {
          ...item,
          files,
        };
      }),
  };
}

async function buildRegistryJsonFile(items: RegistryItem[] = []) {
  // 1. Fix the path for registry items.
  const fixedRegistry = sanitizeRegistry(registry);

  // 3. Write the content of the registry to `registry.json` and public folder
  let registryJson = fixedRegistry;

  registryJson = {
    ...fixedRegistry,
    items: [...fixedRegistry.items, ...items],
  };

  // Create directories if they don't exist
  const publicTargetDir = path.dirname(path.join(process.cwd(), TARGET));

  await fs.mkdir(publicTargetDir, { recursive: true });
  await fs.writeFile(
    path.join(process.cwd(), TARGET),
    JSON.stringify(registryJson, null, 2)
  );
}

async function buildRegistry() {
  return new Promise((resolve, reject) => {
    const process = exec(`yarn shadcn:${isDev ? 'dev' : 'build'}`);

    console.info(`yarn shadcn:${isDev ? 'dev' : 'build'}`);

    process.on('exit', (code) => {
      if (code === 0) {
        resolve(undefined);
      } else {
        reject(new Error(`Process exited with code ${code}`));
      }
    });
  });
}

try {
  if (!isDev) {
    console.info('🗂️ Building registry/__index__.tsx...');
    await buildRegistryIndex();

    // Clean up the entire public/r directory first
    rimraf.sync(path.join(process.cwd(), 'public/r'));
  }

  console.info('📖 Building registry-docs.json...');
  const docsItems = await buildDocsRegistry();

  console.info('💅 Building registry.json...');
  if (MERGE_DOCS) {
    console.info('🔄 Merging docs into registry.json');
    await buildRegistryJsonFile(docsItems);
  } else {
    await buildRegistryJsonFile();
  }

  console.info(`🏗️ Building ${TARGET.replace('/registry.json', '')}...`);
  await buildRegistry();
} catch (error) {
  console.error(error);
  process.exit(1);
}
